# !/usr/bin/env python3
# -*- coding: utf-8 -*-


import xml.etree.ElementTree as EleTree
from util.lib import pretty_xml, compute_time_slot_num
from util.topo_adapter import get_topo_set, get_time_slot_len, add_src_node_port_to_dst_set
from util.flow_adapter import compute_sched_period


def _init_gcl_set(plan_xml):
    gcl_set = []
    tree = EleTree.parse(plan_xml)
    link_ele_set = tree.findall('link')
    for link_ele in link_ele_set:
        link_id = link_ele.findtext('link_id')
        link_id = int(link_id)
        macrotick_id_ele_set = link_ele.iter("macrotick_id")
        macrotick_id_set = [int(macrotick_id_ele.text)
                            for macrotick_id_ele in list(macrotick_id_ele_set)]
        st_queue_id_ele_set = link_ele.iter("st_queue_id")
        st_queue_id_set = [int(st_queue_id_ele.text) for st_queue_id_ele in list(st_queue_id_ele_set)]
        gcl = []
        for macrotick, st_queue in zip(macrotick_id_set, st_queue_id_set):
            gcl.append({'macrotick_id': macrotick, 'priority_queue': st_queue})
        if gcl != []:
            gcl_set.append({'link_id': link_id, 'gcl': gcl})

    return gcl_set


def _update_all_link_into_gcl_set(topo_set,
                                  gcl_set):
    topo_set.sort(key=lambda x: x['link_id'])
    gcl_set.sort(key=lambda x: x['link_id'])
    topo_set_tmp = [item['link_id'] for item in topo_set]
    gcl_set_tmp = [item['link_id'] for item in gcl_set]

    link_id_tmp = []
    for link_id in topo_set_tmp:
        if link_id not in gcl_set_tmp:
            link_id_tmp.append(link_id)

    for link_id in link_id_tmp:
        gcl_set.append({'link_id': link_id,
                        'gcl': []})
    return


def _gather_node(gcl_set):
    node_set = []
    for entry in gcl_set:
        node_id = entry['src_node_id']
        if node_id not in node_set:
            node_set.append(node_id)

    gcl_set_tmp = gcl_set
    gcl_set = [{'node_id': node_id,
                'gcl_port0': [],
                'gcl_port1': [],
                'gcl_port2': [],
                'gcl_port3': [],
                'gcl_port4': [],
                'gcl_port5': [],
                'gcl_port6': [],
                'gcl_port7': [],
                'gcl_port8': [],
                'gcl_port9': [],
                'gcl_port10': [],
                'gcl_port11': [],
                'gcl_port12': [],
                'gcl_port13': [],
                'gcl_port14': [],
                'gcl_port15': [],
                'gcl_port16': [],
                'gcl_port17': [],
                'gcl_port18': [],
                'gcl_port19': []
                } for node_id in node_set]

    for entry_tmp in gcl_set_tmp:
        node_id = entry_tmp['src_node_id']
        for entry in gcl_set:
            if entry['node_id'] == node_id:
                port_id = entry_tmp['src_port_id']
                key = 'gcl_port%d' % port_id
                entry[key] = entry_tmp['gcl']

    return gcl_set


def _trans_priority_queue_to_gate_state(gcl_set):
    for gcl_each_node in gcl_set:
        for port in range(20):
            port_index = 'gcl_port%d' % port
            gcl = gcl_each_node[port_index]
            # 如果某个端口的gcl表项为空
            if not gcl:
                # gcl_each_node[port_index] = [0x1f for i in range(time_slot_num)]
                pass
            else:
                # gcl_each_node[port_index] = [0x1f for i in range(time_slot_num)]
                for entry in gcl:
                    slot_id = entry['macrotick_id']
                    priority_queue = entry['priority_queue']
                    # 0对应队列7（1000 0000），1对应队列队列6（0100 0000），
                    # 2对应队列5（0010 0000）
                    # 除了ST队列之外，其余门控全开
                    gate_state = 0x1 << (7 - priority_queue)
                    gate_state = 0xff & gate_state
                    gate_state = gate_state | 0x1f
                    del entry['priority_queue']
                    entry.update({'gate_state': gate_state})
    return


def _generate_gcl_tbl(gcl_set, time_slot_num, port_num_set):
    # gcl_tbl = GCLTbl()
    gcl_tbl = []

    for each_node in gcl_set:
        port_num = 0
        for port_each_node in port_num_set:
            if port_each_node['node_id'] == each_node['node_id']:
                port_num = port_each_node['port_num']
                each_node.update({'port_num': int(port_num)})
        for port in range(port_num):
            port_index = 'gcl_port%d' % port
            # print(port_num)
            # gcl_each_port = GCLEachPort(time_slot_num)
            gcl_each_port = [0x1f for i in range(time_slot_num)]

            if not each_node[port_index]:
                each_node[port_index] = gcl_each_port
            else:
                for entry in each_node[port_index]:
                    # gcl_each_port.set_gate_state(entry['macrotick_id'], entry['gate_state'])
                    gcl_each_port[entry['macrotick_id']] = entry['gate_state']
                each_node[port_index] = gcl_each_port
        # gcl_each_node = GCLEachNode(**each_node)
        # gcl_tbl.append_gcl_each_node(gcl_each_node)
        gcl_tbl.append(each_node)

    return gcl_tbl


def _get_port_num_each_node(para_xml):
    tree = EleTree.parse(para_xml)
    node_id_ele_set = tree.iter('node_id')
    port_num_ele_set = tree.iter('port_num')
    node_id_ele_set = list(node_id_ele_set)
    port_num_ele_set = list(port_num_ele_set)
    port_num_set = [{'node_id': int(node_id.text),
                     'port_num': int(port_num.text)}
                    for node_id, port_num in
                    zip(node_id_ele_set, port_num_ele_set)]
    return port_num_set


def _init_gcl_tbl_xml(gcl_tbl, gcl_tbl_xml):
    root = EleTree.Element('gcl_tbl')
    for entry in gcl_tbl:
        node_ele = EleTree.SubElement(root, 'node')
        node_id_ele = EleTree.SubElement(node_ele, 'node_id')
        node_id_ele.text = str(entry['node_id'])
        port_num = entry['port_num']

        port_set = ['gcl_port%d' % i for i in range(port_num)]

        for port_name in port_set:
            gcl = entry[port_name]
            # 如果gcl为空，说明没有该端口
            if not gcl:
                continue
            port = EleTree.SubElement(node_ele, 'port')
            port_id = EleTree.SubElement(port, 'port_id')
            port_id.text = str(port_name[-1])
            for slot_id in range(len(gcl)):
                port_entry = EleTree.SubElement(port, 'entry')
                slot = EleTree.SubElement(port_entry, 'slot')
                slot.text = str(slot_id)
                gate = EleTree.SubElement(port_entry, 'gate')
                text = bin(gcl[slot_id])[2:]
                text = text.rjust(8, '0')
                gate.text = str(text)

    tree = EleTree.ElementTree(root)
    pretty_xml(root, '\t', '\n')
    tree.write(gcl_tbl_xml,
               encoding='utf-8',
               xml_declaration=True)
    return


def assemble_gcl_tbl(plan_xml,
                     topo_xml,
                     flow_xml,
                     para_xml,
                     gcl_tbl_xml):
    # 1.从topo_xml中获取topo_set
    topo_set = get_topo_set(topo_xml)
    print("1. topo_set: ")
    [print(entry) for entry in topo_set]
    # 2.计算流的调度周期
    sched_period = compute_sched_period(flow_xml)
    print("2. sched_period: ", sched_period)
    # 3.计算时间槽长度
    time_slot_len = get_time_slot_len(topo_xml)
    print("3. time_slot_len: ", time_slot_len)
    # 4.计算时间槽数目
    time_slot_num = compute_time_slot_num(sched_period, time_slot_len)
    print("4. time_slot_num: ", time_slot_num)
    # 5.获取交换机和网卡的端口数目
    port_num_set = _get_port_num_each_node(para_xml)
    print("5. port_num_set: ")
    [print(entry) for entry in port_num_set]

    # 6.解析plan_xml，记录每条链路上各个时间槽对应的ST队列
    gcl_set = _init_gcl_set(plan_xml)
    print("6. gcl_set: ")
    [print(entry) for entry in gcl_set]

    # 7.根据topo_set中的链路补全plan_xml中没有ST流经过的链路（所有链路都需要配置GCL表）
    _update_all_link_into_gcl_set(topo_set, gcl_set)
    print("7. update all link to gcl_set: ")
    [print(entry) for entry in gcl_set]
    # 8.将gcl_set内的所有link_id转换成node_id+port_id
    add_src_node_port_to_dst_set(gcl_set, topo_set)
    print("8. update src_node, src_port to gcl_set: ")
    [print(entry) for entry in gcl_set]

    # 9. 整理出属于同一节点的gcl表项:若某个port没有经过ST流，将该port的所有时间槽内的priority都置为-1
    gcl_set = _gather_node(gcl_set)

    # 10. 将gcl_set内所有端口的priority_queue转换成门控的value
    _trans_priority_queue_to_gate_state(gcl_set)

    # 初始化门控表结构体GclTbl
    gcl_tbl = _generate_gcl_tbl(gcl_set, time_slot_num, port_num_set)
    # 生成tbl.xml
    _init_gcl_tbl_xml(gcl_tbl, gcl_tbl_xml)

    return


def main():
    plan_xml = '../input/solution.xml'
    topo_xml = '../input/topo_feature.xml'
    flow_xml = '../input/flow_feature.xml'
    para_xml = '../input/cfg_parameter.xml'
    gcl_tbl_xml = '../output/tbl/gcl_tbl.xml'
    assemble_gcl_tbl(plan_xml,
                     topo_xml,
                     flow_xml,
                     para_xml,
                     gcl_tbl_xml)
    return


if __name__ == '__main__':
    main()
